/**
 * Clase de la bola, describe su comportamiento.
 */
import java.util.*;

/**
 * Clase del elemento físico Bola, que hereda de AttachableElement
 * @author Benjamin
 * @version 2
 */
public class Ball extends AttachableElement  {
   private static int id=0;  // Ball identification number
   private double crossSection;
   private final float mass;
   private Vector2D pos_t;     // current position at time t
   private Vector2D pos_tPlusDelta;  // next position in delta time in future
   private Vector2D speed_t;   // speed at time t
   private Vector2D speed_tPlusDelta;   // speed in delta time in future
   private Vector2D a_t;    // acceleration at time t
   private Vector2D a_tMinusDelta;  // acceleration delta time ago;
   private Vector<Spring> springs;  // Vector can grow, arrays cannot. 
   private Vector<RubberBand> rubbers;  // Vector can grow, arrays cannot. 

   /**
    * Constructor
    */
   private Ball(){   // nobody can create a block without state
     super(id++);
     mass=0;
   }
   /**
    * Constructor de la Bola.
    * @param mass masa inicial de la bola.
    * @param position posición inicial de la bola.
    * @param speed vector de velocidad inicial de la bola.
    */
   public Ball(float mass, Vector2D position, Vector2D speed){
      super(id++);
      this.mass = mass;
      pos_t = position;
      speed_t = speed;
      a_t = new Vector2D();
      a_tMinusDelta = new Vector2D();
      springs = new Vector<Spring>();
      rubbers = new Vector<RubberBand>();
      crossSection = 1.0f;
   }
   /**
    * Procedimeinto para adherir un extremo de un resorte a la bola.
    * @param spring resorte a adherir a la bola.
    */
   public void attachSpring (Spring spring) {
     springs.add(spring);
   }
   /**
    * Procedimiento para desacoplar un extremo de un resorte de la bola.
    * @param spring resorte a desacoplar de la bola.
    */
   public void detachSpring (Spring spring) {
     springs.remove(spring);
   }
   /**
    * Procedimeinto para adherir un extremo de un elástico a la bola.
    * @param rubber elástico a adherir a la bola.
    */
   public void attachRubber (RubberBand rubber) {
     rubbers.add(rubber);
   }
   /**
    * Procedimiento para desacoplar un extremo de un elástico de la bola.
    * @param rubber elástico a desacoplar de la bola.
    */
   public void detachRubber (RubberBand rubber) {
     rubbers.remove(rubber);
   }
   /**
    * Función que entrega el vector de posición de la bola.
    * @return vector de posición de la bola.
    */
   public Vector2D getPosition() {
      return pos_t;
   }
   /**
    * Función que entrega la coordenada X de la posición de la bola.
    * @return coordenada X de la posición de la bola.
    */
   public double getX() {
      return pos_t.getX();
   }
   /**
    * Función que entrega la coordenada Y de la posición de la bola.
    * @return coordenada Y de la posición de la bola.
    */
   public double getY() {
      return pos_t.getY();
   }
   /**
    * Función que establece la posición de la bola.
    * @param x coordenada X de la posición a establecer.
    * @param y coordenada Y de la posición a establecer.
    */
   public void setPosition(int x, int y){
      pos_t.set(x,y);
   }
   /**
    * Función para establecer el área de la sección de la bola, a utilizar como medida
    * de cálculo de la fuerza ejecida por el medio viscoso.
    * @param cs sección de la bola.
    */
   public void setCrossSection(double cs) {
      crossSection = cs;
   }
   /**
    * Función que entrega la fuerza neta ejercida en la bola
    * @param g vector de fuerza de gravedad.
    * @param viscosity viscosidad del medio.
    * @return vector de fuerza neta.
    */
   private Vector2D getNetForce(Vector2D g, float viscosity ) {
     Vector2D force = new Vector2D();
     for (Spring s: springs)
         force = force.plus(s.getForce(this));
     for (RubberBand r: rubbers)
         force = force.plus(r.getForce(this));
     force = force.plus(g.times(mass));
     force = force.minus(speed_t.times(viscosity*crossSection)); 
     return(force);    
   }
   /**
    * Función que configura el estado inicial de la bola.
    * @param g vector de fuerza de gravedad.
    * @param viscosity viscosidad dle medio.
    */
   public void setInitialState(Vector2D g, float viscosity) {
      a_tMinusDelta = getNetForce(g, viscosity).times(1/mass);
   }
   /**
    * Función que calcula el siguiente estado de la fuerza y velocidad de la bola,
    * en un delta de tiempo siguiente.
    * @param delta_t tiempo entre cálculos de los estados.
    * @param g vector de gravedad.
    * @param viscosity viscosidad del medio.
    */
   public void computeNextState(double delta_t, Vector2D g, float viscosity) {
     a_t = getNetForce(g, viscosity).times(1/mass);
     speed_tPlusDelta = speed_t.plus(a_t.times(3).minus(a_tMinusDelta).times(delta_t/2));
     pos_tPlusDelta = pos_t.plus(speed_t.times(delta_t)).plus(a_t.times(4).minus(a_tMinusDelta).times(delta_t*delta_t/6));
   }
   /**
    * Función que establece los estados actuales con los estados calculados para
    * un delta de tiempo siguiente.
    */
   public void updateState(){
     pos_t = pos_tPlusDelta;
     speed_t = speed_tPlusDelta;
     a_tMinusDelta = a_t;
   }
   /**
    * Función que entrega la descripción de la bola, junto a su posición.
    * @return string con la identificación y posición de la bola.
    */
   public String getDescription() {
      return "Ball_" + getId()+":"+Vector2D.getDescription();
   }
   /**
    * Función que entrega la la posición de la bola en un string.
    * @return string con la posción de la bola.
    */
   public String getState() {
      return getPosition()+"";
   }
   /**
    * Funcion que resetea la velocidad de la bola.
    */
   public void reset() {
       speed_t.set(0, 0); // velociadad la lleva a cero
   }
}
